package com

import com.core.Generation
import com.utils.CompressUtils
import com.utils.WebpUtils
import org.dom4j.Document
import org.dom4j.DocumentHelper
import org.dom4j.Element
import org.dom4j.io.OutputFormat
import org.dom4j.io.SAXReader
import org.dom4j.io.XMLWriter
import java.io.FileWriter
import java.nio.file.Path
import kotlin.io.path.*

class GenerationXml(override val fileInfo: FileInfo) : Generation(fileInfo) {

    private lateinit var document: Document
    private lateinit var root: Element

    /**
     * 在文件构建阶段 不能处理的变化缓存起来
     */
    private val cacheEvent = mutableListOf<CacheWatch>()

    /**
     * 是否在文件验证阶段
     */
    private var isVerify = false
    override fun createNow() {
        logger.info("创建文件： ${fileInfo.outFilePath}")
        document = DocumentHelper.createDocument()
        root = document.addElement("root")
        writeXml()
    }

    override fun loadFile() {
        val reader = SAXReader()
        document = reader.read(fileInfo.outFilePath.toFile())
        root = document.rootElement
    }

    @OptIn(ExperimentalPathApi::class)
    override fun verify() {
        isVerify = true
        val verifyFile = mutableListOf<String>()
        logger.info("验证路径 ${fileInfo.watchFile}")
        allFiles(fileInfo.watchFile) { path ->
            if (fileInfo.cmpressPng.enable && path.extension.equalsAnyIgnoreCase(
                    "jpg",
                    "png"
                ) && path.pathString.endsWith(fileInfo.cmpressPng.suffix + ".${path.extension}")
            ) {
                // 如果启动了压缩功能 如果这个图片是使用压缩结尾的  那么拒绝检验
                logger.debug("图片是压缩图片 {}", path)
                return@allFiles
            }
            val crc = parseCrc(path)
            logger.debug("verify： {}  {}", path, crc)
            verifyFile.add(parseUrl(path))
            val hasChange = verifyFile(path, crc)
            // 有变动
            // 验证图片
            if (path.extension.equalsAnyIgnoreCase("png", "jpg", "webp")) {
                val compressData by lazy {
                    CompressData(
                        path,
                        fileInfo.cmpressPng.suffix,
                        fileInfo.cmpressPng.quality
                    )
                }

                if (path.extension == "png") {
                    if (fileInfo.cmpressPng.enable) {
                        if (!fileInfo.cmpressPng.isVerifyCompress(path) && (hasChange || compressData.getCompressPath()
                                .notExists())
                        ) {
                            // 确保不是压缩后的资源文件
                            logger.debug("verify：图片有变动，执行压缩 {}", path)
                            CompressUtils.compressImage(compressData)
                        }
                    } else {
                        if (fileInfo.cmpressPng.autoClean && fileInfo.cmpressPng.isVerifyCompress(path)) {
                            // 在未启动压缩资源的时候 该文件是压缩文件 执行删除
                            logger.debug("verify：删除非压缩模式中，存在的压缩图片资源 {}", path)
                            path.deleteRecursively()
                        }
                    }
                }

                val convertWebp by lazy {
                    ConvertWebp(
                        path,
                        fileInfo.webp.webpQuality,
                        fileInfo.webp.compress
                    )
                }
                if (fileInfo.webp.enable) {
                    if (path.extension.equalsAnyIgnoreCase("png", "jpg") && (hasChange || convertWebp.getConvertPath()
                            .notExists())
                    ) {
                        logger.debug("verify：图片有变动，执行toWebp {}", path)
                        WebpUtils.toWebp(convertWebp)
                    }
                } else {
                    if (fileInfo.webp.autoClean && path.extension.lowercase() == "webp") {
                        // 在未启动web转换的时候 该文件是webp文件 执行删除
                        logger.debug("verify：删除非webp转换模式中，存在的转换图片资源 {}", path)
                        path.deleteRecursively()
                    }
                }

            }
        }
        // 查看是否有删除的文件
        var removeNum = 0
        document.selectNodes("//res").filterIsInstance<Element>().forEach {
            val url = it.attributeValue("url")
            if (!verifyFile.contains(url)) {
                logger.debug("文件被删除: url: {} ", url)
                // 不存在url 表示已经被删除了
                it.parent.remove(it)
                removeNum++

                val deleteFile = fileInfo.watchFile.resolve(url)
                // 验证图片
                if (deleteFile.extension.equalsAnyIgnoreCase("png", "jpg")) {
                    if (fileInfo.cmpressPng.enable && deleteFile.extension == "png" && !fileInfo.cmpressPng.isVerifyCompress(
                            deleteFile
                        )
                    ) {
                        // 确保不是压缩后的资源文件
                        CompressData(deleteFile, fileInfo.cmpressPng.suffix).getCompressPath()
                            .also { delete ->
                                logger.debug("图片有变动，删除压缩文件 {}", delete)
                            }.deleteRecursively()
                    }
                    if (fileInfo.webp.enable) {
                        logger.debug("图片有变动，执行toWebp {}", it)
                        ConvertWebp(deleteFile).getConvertPath()
                            .also { delete ->
                                logger.debug("图片有变动，删除toWebp文件 {}", delete)
                            }.deleteRecursively()
                    }
                }
            }
        }
        if (removeNum > 0) {
            logger.debug("本次删除文件 $removeNum 个")
            writeXml()
        }
        isVerify = false
        cacheEvent.removeAll {
            when (it.type) {
                0 -> watchAddOrUpdate(it.content)
                1 -> watchDelete(it.content)
            }
            true
        }
    }

    override fun clear() {
        logger.info("清除xml节点的所有以构建数据")
        document.remove(root)
        root = document.addElement("root")
    }

    private fun verifyFile(path: Path, crc: String): Boolean {
        val url = parseUrl(path)
        logger.debug("验证路径: {} url: {} ", path, url)
        return addNowFileOrUpdate(path, url, crc)
    }

    @Synchronized
    override fun addNowFileOrUpdate(path: Path, url: String?, crc: String?): Boolean {
        val newUrl = url ?: parseUrl(path)
        val newCrc = crc ?: parseCrc(path)

        var result = false
        val node = document.selectNodes("//res[@url='$newUrl']")
        if (node.isEmpty()) { // 不存在
            logger.debug("新增文件：{} crc:{}", path, newCrc)
            root.addElement("res")
                .addAttribute("key", path.name)
                .addAttribute("url", newUrl)
                .addAttribute("crc", newCrc)
            result = true
        } else { // 存在执行更新流程
            node.filterIsInstance<Element>()
                .forEach {
                    val oldCrc = it.attributeValue("crc")
                    if (oldCrc != newCrc) {
                        it.addAttribute("crc", newCrc)
                        logger.debug("更新文件:{} crc:{} last:{}", path, newCrc, oldCrc)
                        result = true
                    } else {
                        logger.debug("crc相同不处理:{}  crc:{}", path, newCrc)
                    }
                }
        }
        return result.okSelf {
            writeXml()
        }

    }

    override fun hasAttByUrl(path: Path): Boolean {
        val value = parseUrl(path) + "/"
        return document.selectNodes("//res[starts-with(@url, '$value')]").isNotEmpty()
    }

    @OptIn(ExperimentalPathApi::class)
    override fun watchAddOrUpdate(filePath: Path) {
        if (isVerify) {
            cacheEvent.add(CacheWatch(0, filePath))
            return
        }
        if (!filePath.exists()) {
            logger.info("更新文件不存在，执行删除文件记录 $filePath")
            watchDelete(filePath)
        } else if (!filePath.isDirectory() && !fileInfo.ignorePath(filePath)) { // 是文件
            runCatching {
                if (filePath.pathString.endsWith(fileInfo.cmpressPng.suffix + ".${filePath.extension}")) {
                    // 如果这个图片是使用压缩功能标志结尾的  那么拒绝
                    return
                }
                // 对图片资源的处理
                if (filePath.extension.equalsAnyIgnoreCase("png", "jpg", "webp")) {
                    if (filePath.extension == "png") {
                        if (fileInfo.cmpressPng.enable) {
                            logger.debug("进入压缩流程-> $filePath")
                            // 压缩资源文件
                            val compress = CompressData(
                                filePath,
                                fileInfo.cmpressPng.suffix,
                                fileInfo.cmpressPng.quality
                            )
                            val compressPath = compress.getCompressPath()
                            if (compressPath.exists()) {
                                logger.debug("存在压缩文件,执行删除激活重新生成 $compressPath")
                                // 如果存在压缩好的文件  直接删除 触发自动重新生成
                                compressPath.deleteRecursively()
                            } else {
                                logger.debug("不存在压缩文件,执行压缩 $compressPath")
                                CompressUtils.compressImage(compress)
                            }
                        } else {
                            if (fileInfo.cmpressPng.autoClean && fileInfo.cmpressPng.isVerifyCompress(filePath)) {
                                // 在未启动压缩资源的时候 该文件是压缩文件 执行删除
                                logger.debug("verify：删除非压缩模式中，存在的压缩图片资源 {}", filePath)
                                filePath.deleteRecursively()
                            }
                        }
                    }

                    if (fileInfo.webp.enable) {
                        logger.debug("进入toWebp流程-> $filePath")
                        if (filePath.extension.equalsAnyIgnoreCase("png", "jpg")) {
                            val cwebp = ConvertWebp(filePath, fileInfo.webp.webpQuality, fileInfo.webp.compress)
                            val convertPath = cwebp.getConvertPath()
                            if (convertPath.exists()) {
                                // 如果存在转换后的文件  直接删除 触发自动重新生成
                                convertPath.deleteRecursively()
                            } else WebpUtils.toWebp(cwebp)
                        }
                    } else {
                        if (fileInfo.webp.autoClean && filePath.extension.lowercase() == "webp") {
                            // 在未启动web转换的时候 该文件是webp文件 执行删除
                            logger.debug("verify：删除非webp转换模式中，存在的转换图片资源 {}", filePath)
                            filePath.deleteRecursively()
                        }
                    }
                }
                val hashValue = Integer.toHexString(Crc32.crc32(filePath))
                logger.info("更新文件 File: $filePath - Hash: $hashValue")
                addNowFileOrUpdate(filePath, crc = hashValue)

            }.onFailure {
                logger.error("更新文件报错", it)
            }

        } else {
            logger.debug("忽略文件 {}", filePath)
        }
    }

    @OptIn(ExperimentalPathApi::class)
    override fun watchDelete(filePath: Path) {
        if (isVerify) {
            cacheEvent.add(CacheWatch(1, filePath))
            return
        }
        if (!fileInfo.ignorePath(filePath)) {
            logger.info("delete File: $filePath ")
            removeFile(filePath)
            // 对图片资源的处理
            if (filePath.extension.equalsAnyIgnoreCase("png", "jpg", "webp")) {
                // 如果不是压缩后的png图片 那么需要删除压缩后生成的图片
                if (fileInfo.cmpressPng.enable && filePath.extension == "png") {
                    if (fileInfo.cmpressPng.isVerifyCompress(filePath)) {
                        logger.debug("删除的是压缩后的图片，那么重新生成压缩图片 {}", filePath)
                        if (filePath.notExists()) {
                            val oldPath = filePath.parent.resolve(filePath.name.replace(fileInfo.cmpressPng.suffix, ""))
                            if (oldPath.exists()) {
                                CompressUtils.compressImage(
                                    CompressData(
                                        oldPath,
                                        fileInfo.cmpressPng.suffix,
                                        fileInfo.cmpressPng.quality
                                    )
                                )
                            }
                        } else {
                            logger.debug(
                                "删除的压缩图片，还在表示可能撤销删除或又一次生成，本次重新生成取消 {}",
                                filePath
                            )
                        }
                    } else {
                        // 确保不是压缩后的资源文件
                        CompressData(filePath, fileInfo.cmpressPng.suffix).getCompressPath().deleteRecursively()
                    }
                }
                ifRemoveWebp(filePath)
            }
        } else {
            logger.debug("已被忽略的文件: {} ", filePath)
            ifRemoveWebp(filePath)
        }
    }

    /**
     * 如果被删除的是wbep文件 进行特殊处理
     */
    @OptIn(ExperimentalPathApi::class)
    private fun ifRemoveWebp(filePath: Path) {
        // 如果启动了webp转换 那么需要删除转换后的图片
        if (fileInfo.webp.enable) {
            if (filePath.pathString.endsWith(".webp", true)) {
                val oldPath = filePath.parent.resolve(filePath.name.replace(".webp", ""))
                logger.debug("删除webp文件({}) 从新生成 {}", filePath, oldPath)
                if (filePath.notExists()) {
                    if (oldPath.exists()) {
                        WebpUtils.toWebp(ConvertWebp(oldPath, fileInfo.webp.webpQuality, fileInfo.webp.compress))
                    }
                } else {
                    logger.debug("删除的webp图片，还在表示可能撤销删除或又一次生成，本次重新生成取消 {}", filePath)
                }
            } else ConvertWebp(filePath).getConvertPath().deleteRecursively()
        }
    }

    override fun removeFile(path: Path) {
        val url = parseUrl(path)
        val deleteList = if (path.extension.isEmpty()) {
            val startWith = "$url/"
            document.selectNodes("//res[starts-with(@url, '$startWith')]").onEach {
                val el = it as Element
                logger.debug("删除文件: {}", el.attributeValue("url"))
                it.parent.remove(it)
            }
        } else {
            document.selectNodes("//res[@url='$url']").onEach {
                val el = it as Element
                logger.debug("删除文件:{}", el.attributeValue("url"))
                it.parent.remove(it)
            }
        }
        deleteList.let {
            if (it.isNotEmpty()) {
                writeXml()
            } else {
                logger.debug("未找到要删除的文件 {} {}", url, path)
            }
        }
    }

    fun writeXml() {
        // lets write to a file
        logger.debug("保存文件： {}", fileInfo.outFilePath)
        FileWriter(fileInfo.outFilePath.toFile())
            .use {
                val format = OutputFormat.createPrettyPrint()
                format.isNewLineAfterDeclaration = false
                val writer = XMLWriter(it, format)
                writer.write(document)
                writer.close()
            }
    }


    /**
     * 解析URL。
     * 该函数将给定的路径字符串与本地根路径进行比较，移除本地根路径部分，返回处理后的URL字符串。
     *
     * @param path 表示待处理的路径对象。
     * @return 处理后的URL字符串。
     */
    private fun parseUrl(path: Path): String {
        var url = path.pathString // 将传入的路径字符串赋值给url变量

        // 遍历并移除本地根路径中的每个路径部分
        fileInfo.localRootPath.forEach {
            url = url.replace(it, "")
        }

        return url.replace("\\", "/") // 返回处理后的URL字符串
    }

    /**
     * 解析给定路径的CRC校验。
     *
     * @param path 表示文件路径的Path对象。
     * @return 校验和的十六进制字符串表示。
     */
    private fun parseCrc(path: Path): String {
        // 计算路径对应的CRC32校验和
        val hashValue = Crc32.crc32(path)
        // 将计算得到的整型校验和转换为十六进制字符串
        val hash = Integer.toHexString(hashValue)
        return hash
    }


}